﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Simple.HttpServer {
    public class Server {

        private HttpListener _listener;
        private Configuration _configuration;
        private ResourceManager _resourceManager;

        public EventHandler<ProcessableGenericTaskEventArgs<HttpListenerContext>> RequestArrived;
        public EventHandler<ResponseSentEventArgs> ResponseSent;

        public Server(Configuration configuration) {
            _configuration = configuration;
            _resourceManager = new ResourceManager(_configuration);
        }

        public void Start() {
            _listener = new HttpListener();
            _listener.Prefixes.Add(_configuration.Prefix);
            _listener.IgnoreWriteExceptions = true;
            _listener.Start();
            StartContextWait();
        }

        private async void StartContextWait() {
            Task<HttpListenerContext> contextTask = null;
            var contextWaitStarted = false;
            try {
                contextTask = _listener.GetContextAsync();
                var context = await contextTask;
                if (_listener.IsListening) {
                    contextWaitStarted = true;
                    StartContextWait();
                }
                ProcessContextTask(contextTask);
            } catch {
                // Awaiting/Processing failed...
            }
            if (!contextWaitStarted) {
                if (_listener.IsListening) {
                    StartContextWait();
                }
            }
        }

        private void GetContextCallback(IAsyncResult iar) {
            if (_listener.IsListening) {
                StartContextWait();
            }
            var listener = iar.AsyncState as HttpListener;
            try {
                var context = listener.EndGetContext(iar);
                Thread.Sleep(TimeSpan.FromSeconds(5));
                //var requestArrivedArgs = new ProcessableGenericTaskEventArgs<HttpListenerContext>(task);
                //OnRequestArrived(requestArrivedArgs);
                //if (requestArrivedArgs.Processed) {
                //    return;
                //}

                //var context = task.Result;
                var request = context.Request;
                var response = context.Response;
                var uri = request.Url;
                ResourceData rd;
                try {
                    rd = _resourceManager.ResourceProvider.GetResource(uri);
                    if (rd != null
                        && rd.Stream != null) {
                        SetResourceDataToContext(context, rd);
                        rd.Stream.CopyTo(response.OutputStream, 4096);
                    } else {
                        rd = new ResourceData();
                        rd.Exception = new Exception("Internal server error");
                        WriteError(context, rd.Exception);
                    }
                } catch (Exception ex) {
                    rd = new ResourceData();
                    rd.Exception = ex;
                    WriteError(context, rd.Exception);
                }

                OnResponseSent(rd, context);
                context.Response.Close();
                if (rd.Stream != null) {
                    rd.Stream.Close();
                }
            } catch {
            }
        }

        private void ProcessContextTask(Task<HttpListenerContext> task) {
            //Thread.Sleep(1000);
            var requestArrivedArgs = new ProcessableGenericTaskEventArgs<HttpListenerContext>(task);
            OnRequestArrived(requestArrivedArgs);
            if (requestArrivedArgs.Processed) {
                return;
            }

            var context = task.Result;
            var request = context.Request;
            var response = context.Response;
            var uri = request.Url;
            ResourceData rd;
            try {
                rd = _resourceManager.ResourceProvider.GetResource(uri);
                if (rd != null
                    && rd.Stream != null) {
                    SetResourceDataToContext(context, rd);
                    rd.Stream.CopyTo(response.OutputStream, 4096);
                } else {
                    rd = new ResourceData();
                    rd.Exception = new Exception("Internal server error");
                    WriteError(context, rd.Exception);
                }
            } catch (Exception ex) {
                rd = new ResourceData();
                rd.Exception = ex;
                WriteError(context, rd.Exception);
            }

            OnResponseSent(rd, context);
            context.Response.Close();
            if (rd.Stream != null) {
                rd.Stream.Close();
            }
        }

        private void WriteError(HttpListenerContext context, Exception ex) {
            var rd = _resourceManager.ResourceProvider.GetExceptionResource(ex);
            SetResourceDataToContext(context, rd);
            rd.Stream.CopyTo(context.Response.OutputStream);
        }

        private void SetResourceDataToContext(HttpListenerContext context, ResourceData rd) {
            context.Response.ContentLength64 = rd.ContentLength;
            context.Response.ContentType = rd.ContentType;
            context.Response.StatusCode = (int)rd.StatusCode;
        }

        protected virtual void OnResponseSent(ResourceData args, HttpListenerContext context) {
            RaiseEvent(ResponseSent, () => new ResponseSentEventArgs(args, context));
        }

        protected virtual void OnRequestArrived(ProcessableGenericTaskEventArgs<HttpListenerContext> args) {
            RaiseEvent(RequestArrived, () => args);
        }

        protected void RaiseEvent<TEventArgs>(EventHandler<TEventArgs> evt, Func<TEventArgs> eventArgsFactoryFunc)
            where TEventArgs : EventArgs {
            var handler = evt;
            if (handler != null) {
                handler(this, eventArgsFactoryFunc());
            }
        }

        public void Stop() {
            if (_listener != null) {
                _listener.Stop();
            }
        }
    }
}
